package org.mc.sbd.redis.api;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("rdl")
@Slf4j
public class RedisDistributedLockController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 以扣减库存场景为例
     *
     * @param productId 产品标识
     */
    @PostMapping("reduce/{productId}/stock")
    public void reduceStock(@PathVariable Long productId) throws Exception {
        String productLockKey = productId.toString();
        String productStockKey = productId + "stock";
//        useRedis(productLockKey, productStockKey);
        useRedisson(productLockKey, productStockKey);
    }

    private void useRedisson(String productLockKey, String productStockKey) throws Exception {
        RLock redissonLock = redissonClient.getLock(productLockKey);

        redissonLock.lock();
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get(productStockKey));

            if (stock > 0) {
                int realStock = stock - 1;

                stringRedisTemplate.opsForValue().set(productStockKey, String.valueOf(realStock));

                log.info("剩余库存：" + realStock);
            } else {
                throw new Exception("库存不足");
            }
        } finally {
            redissonLock.unlock();
        }
    }

    private void useRedis(String productLockKey, String productStockKey) throws Exception {
        String lockReqId = UUID.randomUUID().toString();
        // 这里的超时时间，在高并发情况下，还是会出现当前线程执行时间超过锁生命时长的情况，需要用redisson续签锁的时间
        Boolean hasGotLock = stringRedisTemplate.opsForValue().setIfAbsent(productLockKey, lockReqId, 10, TimeUnit.SECONDS);

        if (hasGotLock) {
            try {
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get(productStockKey));

                if (stock > 0) {
                    int realStock = stock - 1;

                    stringRedisTemplate.opsForValue().set(productStockKey, String.valueOf(realStock));

                    log.info("剩余库存：" + realStock);
                } else {
                    throw new Exception("库存不足");
                }
            } finally {
                if (lockReqId.equals(stringRedisTemplate.opsForValue().get(productLockKey))) {
                    stringRedisTemplate.delete(productLockKey);
                }
            }
        } else {
            throw new Exception();
        }
    }
}